﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.Concurrent;
using System.Collections;
using System.Threading.Tasks;

namespace Tsual.DataPipeline
{
    public static class DictionaryPipeExtensions
    {
        public static IDictionary<TKey, TValue> CreateDictionary<TKey, TValue>(this PipeObjectFactory factory, string FactoryKey, IDictionary<TKey, TValue> Instance = null)
        {
            if (factory.ContainsKey(FactoryKey))
                return factory[FactoryKey] as IDictionary<TKey, TValue>;

            if (Instance == null)
                Instance = new Dictionary<TKey, TValue>();

            factory.AddOrUpdate(FactoryKey, Instance, (k, o) => o);

            return Instance;
        }
    }

    public struct ExpirablePipeValue<T> :IConvertible<T>
    {
        public T Value { get; set; }
        public DateTimeOffset? AbsoluteExpireTime { get; set; }
        public T Convert() => Value;
    }

    public abstract class AbstractDictionaryPipe<TKey, TValue> : IDataPipeDescription<TValue, TKey>
    {
        protected abstract string DictionaryName { get; }

        protected AbstractDictionaryPipe(PipeObjectFactory factory)
        {
            map = (factory ?? throw new ArgumentNullException(nameof(factory))).CreateDictionary<TKey, TValue>(DictionaryName ?? throw new ArgumentNullException(nameof(DictionaryName)));
        }

        IDictionary<TKey, TValue> map;

        public bool CanRead(TKey meta) => true;

        public bool CanWrite(TValue data, TKey meta) => true;

        public Task<DataPipeReadResult<TValue>> Read(TKey meta) => Task.Run(() => map.ContainsKey(meta) ? DataPipeReadResult<TValue>.Success(map[meta]) : DataPipeReadResult<TValue>.Notfound());

        public Task<DataPipeWriteResult> Write(TValue data, TKey meta) => Task.Run(() =>
        {
            if (!map.ContainsKey(meta)) map.Add(meta, data);
            else map[meta] = data;
            return DataPipeWriteResult.Success();
        });
    }

    public class DefaultDictionaryPipe<T, TMeta> : IDataPipeDescription<T, KeyValuePair<string, TMeta>>
    {
        PipeObjectFactory factory;

        public DefaultDictionaryPipe(PipeObjectFactory factory)
        {
            this.factory = factory ?? throw new ArgumentNullException(nameof(factory));
        }

        public bool CanRead(KeyValuePair<string, TMeta> meta) => !string.IsNullOrEmpty(meta.Key);

        public bool CanWrite(T data, KeyValuePair<string, TMeta> meta) => !string.IsNullOrEmpty(meta.Key);

        public Task<DataPipeReadResult<T>> Read(KeyValuePair<string, TMeta> meta) => Task.Run(() =>
        {
            var map = factory.CreateDictionary<TMeta, T>(meta.Key);
            return map.ContainsKey(meta.Value) ? DataPipeReadResult<T>.Success(map[meta.Value]) : DataPipeReadResult<T>.Notfound();
        });

        public Task<DataPipeWriteResult> Write(T data, KeyValuePair<string, TMeta> meta) => Task.Run(() =>
        {
            var map = factory.CreateDictionary<TMeta, T>(meta.Key);
            if (!map.ContainsKey(meta.Value)) map.Add(meta.Value, data);
            else map[meta.Value] = data;
            return DataPipeWriteResult.Success();
        });
    }

    public class ExpirableDictionaryPipe<T, TMeta> : IDataPipeDescription<ExpirablePipeValue<T>, KeyValuePair<string, TMeta>>
    {
        PipeObjectFactory factory;

        public ExpirableDictionaryPipe(PipeObjectFactory factory)
        {
            this.factory = factory ?? throw new ArgumentNullException(nameof(factory));
        }

        public bool CanRead(KeyValuePair<string, TMeta> meta) => !string.IsNullOrEmpty(meta.Key);

        public bool CanWrite(ExpirablePipeValue<T> data, KeyValuePair<string, TMeta> meta) => !string.IsNullOrEmpty(meta.Key);

        public Task<DataPipeReadResult<ExpirablePipeValue<T>>> Read(KeyValuePair<string, TMeta> meta)=>Task.Run(() =>
        {
            var map = factory.CreateDictionary<TMeta, ExpirablePipeValue<T>>(meta.Key);
            if (!map.ContainsKey(meta.Value)) return DataPipeReadResult<ExpirablePipeValue<T>>.Notfound();

            var exobj = map[meta.Value];
            if (exobj.AbsoluteExpireTime > DateTimeOffset.Now)
            {
                return DataPipeReadResult<ExpirablePipeValue<T>>.Success(new ExpirablePipeValue<T>() { Value = exobj.Value, AbsoluteExpireTime = exobj.AbsoluteExpireTime });
            }
            else
            {
                map.Remove(meta.Value);
                return DataPipeReadResult<ExpirablePipeValue<T>>.Notfound();
            }
        });

        public Task<DataPipeWriteResult> Write(ExpirablePipeValue<T> data, KeyValuePair<string, TMeta> meta) => Task.Run(() =>
        {
            var map = factory.CreateDictionary<TMeta, ExpirablePipeValue<T>>(meta.Key);
            if (!map.ContainsKey(meta.Value)) map.Add(meta.Value, new ExpirablePipeValue<T>() { Value = data.Value, AbsoluteExpireTime = data.AbsoluteExpireTime });
            else map[meta.Value] = new ExpirablePipeValue<T>() { Value = data.Value, AbsoluteExpireTime = data.AbsoluteExpireTime };
            return DataPipeWriteResult.Success();
        });
    }
}
